﻿using System;
using System.Linq;
using System.Windows.Input;

namespace LINQPad.OpenAccess.UI.Commands
{
    /// <summary>
    /// Delegated based <see cref="ICommand" /> implementation.
    /// </summary>
    /// <typeparam name="T">The type of the command parameter.</typeparam>
    public sealed class DelegateCommand<T> : ICommand, IDisposable
    {
        #region Private

        #region - Vars

        private Action<T> _executeAction;
        private Func<T, bool> _canExecuteFunction;

        #endregion

        #region - Functions

        /// <summary>
        /// Throws an exception if the specified parameter is not supported.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        /// <exception cref="System.NotSupportedException">If the parameter is not supported.</exception>
        private void ThrowIfParameterNotSupported( object parameter )
        {
            if ( parameter == null || parameter is T )
                return;

            throw new NotSupportedException( String.Format( "Unsupported parameter type: {0} ! (Expected: {1})", parameter.GetType( ).FullName, typeof( T ).FullName ) );
        }

        #endregion

        #endregion

        #region Public

        #region - Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="DelegateCommand{T}" /> class.
        /// </summary>
        /// <param name="execute">The Execute callback.</param>
        /// <param name="canExecute">The CanExecute callback.</param>
        public DelegateCommand( Action execute, Func<bool> canExecute = null )
        {
            if ( execute != null )
                this._executeAction = _ => execute( );
            if ( canExecute != null )
                this._canExecuteFunction = _ => canExecute( );
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="DelegateCommand{T}" /> class.
        /// </summary>
        /// <param name="execute">The Execute callback.</param>
        /// <param name="canExecute">The CanExecute callback.</param>
        public DelegateCommand( Action<T> execute = null, Func<T, bool> canExecute = null )
        {
            this._executeAction = execute;
            this._canExecuteFunction = canExecute;
        }

        #endregion

        #region - Functions

        /// <summary>
        /// Determines whether the command can be executed with the specified parameter.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        /// <returns>
        ///   <c>true</c> if the command can be executed; otherwise, <c>false</c>.
        /// </returns>
        public bool CanExecute( T parameter )
        {
            if ( this._canExecuteFunction == null )
                return true;
            return this._canExecuteFunction( parameter );
        }
        /// <summary>
        /// Executes the command with the specified parameter.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        public void Execute( T parameter )
        {
            if ( this._executeAction == null )
                return;
            this._executeAction( parameter );
        }
        /// <summary>
        /// Raises the 'CanExecuteChanged' event of the command.
        /// </summary>
        public void RaiseCanExecuteChanged( )
        {
            var handler = this.CanExecuteChanged;
            if ( handler != null )
                handler( this, EventArgs.Empty );
        }

        #endregion

        #endregion

        #region ICommand Members

        #region - Functions

        /// <summary>
        /// Defines the method that determines whether the command can execute in its current state.
        /// </summary>
        /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
        /// <returns>
        /// true if this command can be executed; otherwise, false.
        /// </returns>
        bool ICommand.CanExecute( object parameter )
        {
            if ( this._canExecuteFunction == null )
                return true;

            if ( parameter != null )
                this.ThrowIfParameterNotSupported( parameter );

            return this.CanExecute( parameter == null ? default( T ) : ( T ) parameter );
        }
        /// <summary>
        /// Defines the method to be called when the command is invoked.
        /// </summary>
        /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        void ICommand.Execute( object parameter )
        {
            if ( this._executeAction == null )
                return;

            if ( parameter != null )
                this.ThrowIfParameterNotSupported( parameter );

            this.Execute( parameter == null ? default( T ) : ( T ) parameter );
        }

        #endregion

        #region - Events

        /// <summary>
        /// Occurs when the 'CanExecute' state of the command has changed.
        /// </summary>
        public event EventHandler CanExecuteChanged;

        #endregion



        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose( )
        {
            this._canExecuteFunction = null;
            this._executeAction = null;
            GC.SuppressFinalize( this );
        }

        #endregion
    }
}
